
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include "tlg.h"

tlg_t* new_tlg (tlg5_header_t * header)
{
	tlg_t* tlg = new tlg_t;
	tlg->width = header->width;
	tlg->height = header->height;
	tlg->colors = header->colors;
	tlg->buffer = new uint8_t[tlg->width * tlg->height * tlg->colors];
	return tlg;
}

void delete_tlg(tlg_t* tlg)
{
	delete [] tlg->buffer;
	delete tlg;
}

static int TVPTLG5DecompressSlide(uint8_t *out, const uint8_t *in, 
								  int insize, uint8_t *text, int initialr)
{
	int r = initialr;
	unsigned int flags = 0;
	const uint8_t *inlim = in + insize;

	while (in < inlim) {
		if (!((flags >>= 1) & 0x100))
			flags = *in++ | 0xff00;

		if (flags & 1) {
			int mpos = in[0] | ((in[1] & 0xf) << 8);
			int mlen = (in[1] & 0xf0) >> 4;
			in += 2;
			mlen += 3;
			if (mlen == 18)
				mlen += *in++;

			while (mlen--) {
				*out++ = text[r++] = text[mpos++];
				mpos &= (4096 - 1);
				r &= (4096 - 1);
			}
		} else {
			unsigned char c = *in++;
			*out++ = c;
			text[r++] = c;
/*			0[out++] = text[r++] = 0[in++];*/
			r &= (4096 - 1);
		}
	}

	return r;
}
static void TVPTLG5ComposeColors3(uint8_t *outp, const uint8_t *upper, 
								  uint8_t * const *buf, int width)
{
	uint8_t pc[3];
	uint8_t c[3];

	pc[0] = pc[1] = pc[2] = 0;
	for (int x = 0; x < width; x++) {
		c[0] = buf[0][x];
		c[1] = buf[1][x];
		c[2] = buf[2][x];
		c[0] += c[1]; c[2] += c[1];
		outp[0] = ((pc[0] += c[0]) + upper[0]) & 0xff;
		outp[1] = ((pc[1] += c[1]) + upper[1]) & 0xff;
		outp[2] = ((pc[2] += c[2]) + upper[2]) & 0xff;
		outp += 3;
		upper += 3;
	}
}

static void TVPTLG5ComposeColors4(uint8_t *outp, const uint8_t *upper, 
								  uint8_t * const *buf, int width)
{
	uint8_t pc[4];
	uint8_t c[4];

	pc[0] = pc[1] = pc[2] = pc[3] = 0;
	for (int x = 0; x < width; x++) {
		c[0] = buf[0][x];
		c[1] = buf[1][x];
		c[2] = buf[2][x];
		c[3] = buf[3][x];
		c[0] += c[1]; c[2] += c[1];
		*(uint32_t *)outp = ((((pc[0] += c[0]) + upper[0]) & 0xff)      ) +
						((((pc[1] += c[1]) + upper[1]) & 0xff) <<  8) +
						((((pc[2] += c[2]) + upper[2]) & 0xff) << 16) +
						((((pc[3] += c[3]) + upper[3]) & 0xff) << 24);
		outp += 4;
		upper += 4;
	}
}


tlg_t* read_tlg(uint8_t *tlg_raw_data, int tlg_size)
{
	tlg5_header_t *tlg5_header = (tlg5_header_t *)tlg_raw_data;
	int blockcount;
	int insize;
	uint8_t *in, *out;
	uint8_t text[4096];

	tlg_t* tlg = new_tlg(tlg5_header);
	out = tlg->buffer;

	blockcount = (int)((tlg5_header->height - 1) / tlg5_header->blockheight) + 1;
	// skip block size section
	in = (uint8_t *)(tlg5_header + 1) + blockcount * 4;
	insize = tlg_size - sizeof(tlg5_header_t) - blockcount * 4;

	uint8_t *outbuf[4];
	int r = 0;

	memset(text, 0, 4096);

	outbuf[0] = (uint8_t *)malloc(((tlg5_header->blockheight 
			* tlg5_header->width + 10 + 3) & ~3) * tlg5_header->colors);
	if (!outbuf[0]) {
		fprintf(stderr, "Not enough memory\n");
		delete_tlg(tlg);
		return NULL;
	}

	for (unsigned int i = 1; i < tlg5_header->colors; i++)
		outbuf[i] = outbuf[i - 1] + ((tlg5_header->blockheight 
			* tlg5_header->width + 10 + 3) & ~3);

	uint8_t *prevline = NULL;
	for (unsigned int y_blk = 0; y_blk < tlg5_header->height; y_blk += tlg5_header->blockheight) {
		unsigned int c;
		for (c = 0; c < tlg5_header->colors; c++) {
			uint8_t mark;
			uint32_t size;

			mark = *in++;
			size = *(uint32_t *)in;
			in += 4;

			if (!mark)
				// modified LZSS compressed data
				r = TVPTLG5DecompressSlide(outbuf[c], in, size, text, r);
			else
				// raw data
				memcpy(outbuf[c], in, size);

			in += size;
		}

		// compose colors and store
		unsigned int y_lim = y_blk + tlg5_header->blockheight;
		if (y_lim > tlg5_header->height)
			y_lim = tlg5_header->height;

		uint8_t *outbufp[4];
		for (c = 0; c < tlg5_header->colors; c++)
			outbufp[c] = outbuf[c];

		for (unsigned int y = y_blk; y < y_lim; y++) {
			//uint8_t *current = &out[y * ((tlg5_header->width * tlg5_header->colors + 3) & ~3)];
			uint8_t *current = &out[y * tlg5_header->width * tlg5_header->colors];
			uint8_t *current_org = current;

			if (prevline) {
				// not first line
				switch (tlg5_header->colors) {
				case 3:
					TVPTLG5ComposeColors3(current, prevline, outbufp, tlg5_header->width);
					outbufp[0] += tlg5_header->width; outbufp[1] += tlg5_header->width;
					outbufp[2] += tlg5_header->width;
					break;
				case 4:
					TVPTLG5ComposeColors4(current, prevline, outbufp, tlg5_header->width);
					outbufp[0] += tlg5_header->width; outbufp[1] += tlg5_header->width;
					outbufp[2] += tlg5_header->width; outbufp[3] += tlg5_header->width;
					break;
				}
			} else {
				uint8_t pr = 0, pg = 0, pb = 0, pa = 0;
				unsigned int x;

				// first line
				switch(tlg5_header->colors) {
				case 3:
					for (x = 0; x < tlg5_header->width; x++) {
						uint8_t b = outbufp[0][x];
						uint8_t g = outbufp[1][x];
						uint8_t r = outbufp[2][x];

						b += g; r += g;
						*current++ = pb += b;
						*current++ = pg += g;
						*current++ = pr += r;
					}
					outbufp[0] += tlg5_header->width;
					outbufp[1] += tlg5_header->width;
					outbufp[2] += tlg5_header->width;
					break;
				case 4:
					for(x = 0; x < tlg5_header->width; x++) {
						uint8_t b = outbufp[0][x];
						uint8_t g = outbufp[1][x];
						uint8_t r = outbufp[2][x];
						uint8_t a = outbufp[3][x];

						b += g; r += g;
						*current++ = pb += b;
						*current++ = pg += g;
						*current++ = pr += r;
						*current++ = pa += a;
					}
					outbufp[0] += tlg5_header->width;
					outbufp[1] += tlg5_header->width;
					outbufp[2] += tlg5_header->width;
					outbufp[3] += tlg5_header->width;
					break;
				}
			}
			prevline = current_org;
		}
	}
	free(outbuf[0]);

//	if (tlg5_header->colors == 4) {
//		uint8_t *b = out;
//		int pixels = tlg5_header->width * tlg5_header->height;
//		for (int i = 0; i < pixels; ++i) {
//			b[0] = b[0] * b[3] / 255 + (255 - b[3]);
//			b[1] = b[1] * b[3] / 255 + (255 - b[3]);
//			b[2] = b[2] * b[3] / 255 + (255 - b[3]);
//			b += 4;
//		}
//	}
	
	return tlg;
}

tlg_t* read_tlg_file(const char* tlg_path)
{
	FILE* fp = fopen(tlg_path, "rb");
	if (!fp)
	{
		fprintf(stderr, "File doesn't exist: %s\n", tlg_path);
		return NULL;
	};
	fseek(fp, 0L, SEEK_END);
	uint64_t sz = ftell(fp);
	fseek(fp, 0L, SEEK_SET);
	if (sz <= 24)
	{
		fclose(fp);
		fprintf(stderr, "Invalid TLG format: header too short\n");
		return NULL;
	};
	uint8_t* buffer = new uint8_t[sz];
	fread(buffer, 1, sz, fp);
	tlg_t* tlg = read_tlg(buffer, (int)sz); // uint64_t -> int, suppress cast warning
	delete[] buffer;
	if (!tlg)
	{
		fclose(fp);
		fprintf(stderr, "read_tlg error\n");
		return NULL;
	}
	fclose(fp);
	return tlg;
}

